//
//  Canvas.cpp
//  RayTracer
//
//  Created by 俞云康 on 2/15/16.
//  Copyright © 2016 yushroom. All rights reserved.
//

#include "Canvas.hpp"
#include <fstream>
#include "Params.hpp"
#include "FileManager.hpp"
using namespace std;

Canvas Canvas::create(const Params& params) {
    int width  = params.getIntAttribute("width");
    int height = params.getIntAttribute("height");
    return Canvas(width, height);
}

typedef uint8_t BYTE;
typedef uint16_t WORD;
typedef uint32_t DWORD;
typedef int32_t LONG;

#pragma pack(2)
struct BitmapFileHeader {
	WORD	type;			//== 0x424D "BM"
	DWORD	size;
	WORD	reserved1;
	WORD	reserved2;
	DWORD	offBits;
};	// 14 bytes

struct BitmapInfoHeader {
	DWORD	size;		// 40, size of this struct
	LONG	width;		// pixels
	LONG	height;		// pixels
	WORD	planes;		// 1
	WORD	bitcount;	// 1 for binary, 4 for 16bit, 8 for 256bit, 24 for true color
	DWORD	compression;
	DWORD	sizeImage;
	LONG	XPelsPerMeter;
	LONG	YPelsPerMeter;
	DWORD	clrUsed;
	DWORD	clrImportant;
};	// 40 bytes

inline BYTE tobyte(float x) {
	return BYTE(powf(saturate(x), 1 / 2.2f) * 255 + .5f);;
}

inline void write_bytes(ostream& fout, WORD x) {
	fout << BYTE(x) << BYTE(x>>8);
}

inline void write_bytes(ostream& fout, DWORD x) {
	fout << BYTE(x) << BYTE(x>>8) << BYTE(x>>16) << BYTE(x>>24);
}

void write_bmp(
	const string& path,
	const size_t width,
	const size_t height,
	const vector<Color>& pixels) {

	//auto s1 = sizeof(BitmapFileHeader);
	//auto s2 = sizeof(BitmapInfoHeader);
	//assert(sizeof(BitmapFileHeader) == 14);
	assert(sizeof(BitmapInfoHeader) == 40);
	assert(pixels.size() == width * height);
	const size_t bytes = width * height * 3;
	ofstream fout(path, std::ios::binary);
	
	BitmapFileHeader header;
	header.type = 0x4D42;
	header.size = DWORD(54 + bytes);
	header.reserved1 = 0;
	header.reserved2 = 0;
	header.offBits = 54;
	//fout.write((const char*)&header, 14);
	//fout << header.type << header.size << header.reserved1 << header.reserved1 << header.offBits;
	write_bytes(fout, header.type);
	write_bytes(fout, header.size);
	write_bytes(fout, header.reserved1);
	write_bytes(fout, header.reserved2);
	write_bytes(fout, header.offBits);

	BitmapInfoHeader info_header;
	memset(&info_header, 0, sizeof(BitmapInfoHeader));
	info_header.size = 40;
	info_header.height = (LONG)height;
	//info_header.height = -info_header.height;
	info_header.width = (LONG)width;
	info_header.planes = 1;
	info_header.bitcount = 24;
	info_header.sizeImage = (DWORD)bytes;
	fout.write((const char*)&info_header, 40);

	vector<BYTE> p(bytes);
	int i = 0;
	for (int y = (int)height - 1; y >= 0; --y)
		for (int x = 0; x < (int)width; ++x) {
			auto& c = pixels[y*width+x];
			p[i++] = tobyte(c.z);
			p[i++] = tobyte(c.y);
			p[i++] = tobyte(c.x);
			//fout << tobyte(c.z) << tobyte(c.y) << tobyte(c.x);
		}
	fout.write((const char*)&p[0], bytes);
	fout.close();
}

void write_ppm(const string& path, const size_t width, const size_t height, const vector<Color> pixels) {
	FILE *f = fopen(path.c_str(), "w");         // Write image to PPM file.
	fprintf(f, "P3\n%d %d\n%d\n", (int)width, (int)height, 255);
	for (int i = 0; i < int(width*height); i++)
		fprintf(f, "%d %d %d ", toInt(pixels[i].x), toInt(pixels[i].y), toInt(pixels[i].z));
	fclose(f);
}

void Canvas::write(const string& path) {
	info("Write image to: %s\n", path.c_str());
	auto pos = path.rfind('.');
	assert(pos != std::string::npos);
	auto ext = path.substr(pos+1);
	if (ext == "bmp")
		write_bmp(path, m_width, m_height, pixels);
	else
		write_ppm(path, m_width, m_height, pixels);
}